package github.ishaan.buttonprogressbar;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

/**
 * Created by ishaan on 13/3/17.
 */
public class ButtonProgressBar extends Component {
    /**
     * * The constant ButtonProgressBar_bgColor
     */
    public static final String BUTTONPROGRESSBAR_BGCOLOR = "bgColor";
    /**
     * * The constant ButtonProgressBar_progColor
     */
    public static final String BUTTONPROGRESSBAR_PROGCOLOR = "progColor";
    /**
     * * The constant ButtonProgressBar_textColor
     */
    public static final String BUTTONPROGRESSBAR_TEXTCOLOR = "textColor";
    /**
     * * The constant ButtonProgressBar_textSize
     */
    public static final String BUTTONPROGRESSBAR_TEXTSIZE = "textSize";
    /**
     * * The constant ButtonProgressBar_type
     */
    public static final String BUTTONPROGRESSBAR_TYPE = "type";
    /**
     * * The constant ButtonProgressBar_text
     */
    public static final String BUTTONPROGRESSBAR_TEXT = "buttonText";
    /**
     * * The constant STATE_RESET
     */
    private static final int STATE_RESET = -1;
    /**
     * State start
     */
    private static final int STATE_START = 1;
    /**
     * State stop
     */
    private static final int STATE_STOP = 0;
    /**
     * Api level lollipop
     */
    private static final int API_LEVEL_LOLLIPOP = 21;
    /**
     * * The constant DEFAULT_BGCOLOR
     */
    private static final int DEFAULT_BGCOLOR = Color.getIntColor("#0E8DD4");
    /**
     * Default progcolor
     */
    private static final int DEFAULT_PROGCOLOR = Color.getIntColor("#0399E5");
    /**
     * Pre-calculated values [0.0000f - 1.0000f]
     * Used from FastOutSlowInInterpolator, array of values to provide smooth indeterminate loading animation
     */
    private static final float[] INDETERMINATE_ANIMATION_VALUES = new float[]{
            0.0000f, 0.0001f, 0.0002f, 0.0005f, 0.0009f, 0.0014f, 0.0020f,
            0.0027f, 0.0036f, 0.0046f, 0.0058f, 0.0071f, 0.0085f, 0.0101f,
            0.0118f, 0.0137f, 0.0158f, 0.0180f, 0.0205f, 0.0231f, 0.0259f,
            0.0289f, 0.0321f, 0.0355f, 0.0391f, 0.0430f, 0.0471f, 0.0514f,
            0.0560f, 0.0608f, 0.0660f, 0.0714f, 0.0771f, 0.0830f, 0.0893f,
            0.0959f, 0.1029f, 0.1101f, 0.1177f, 0.1257f, 0.1339f, 0.1426f,
            0.1516f, 0.1610f, 0.1707f, 0.1808f, 0.1913f, 0.2021f, 0.2133f,
            0.2248f, 0.2366f, 0.2487f, 0.2611f, 0.2738f, 0.2867f, 0.2998f,
            0.3131f, 0.3265f, 0.3400f, 0.3536f, 0.3673f, 0.3810f, 0.3946f,
            0.4082f, 0.4217f, 0.4352f, 0.4485f, 0.4616f, 0.4746f, 0.4874f,
            0.5000f, 0.5124f, 0.5246f, 0.5365f, 0.5482f, 0.5597f, 0.5710f,
            0.5820f, 0.5928f, 0.6033f, 0.6136f, 0.6237f, 0.6335f, 0.6431f,
            0.6525f, 0.6616f, 0.6706f, 0.6793f, 0.6878f, 0.6961f, 0.7043f,
            0.7122f, 0.7199f, 0.7275f, 0.7349f, 0.7421f, 0.7491f, 0.7559f,
            0.7626f, 0.7692f, 0.7756f, 0.7818f, 0.7879f, 0.7938f, 0.7996f,
            0.8053f, 0.8108f, 0.8162f, 0.8215f, 0.8266f, 0.8317f, 0.8366f,
            0.8414f, 0.8461f, 0.8507f, 0.8551f, 0.8595f, 0.8638f, 0.8679f,
            0.8720f, 0.8760f, 0.8798f, 0.8836f, 0.8873f, 0.8909f, 0.8945f,
            0.8979f, 0.9013f, 0.9046f, 0.9078f, 0.9109f, 0.9139f, 0.9169f,
            0.9198f, 0.9227f, 0.9254f, 0.9281f, 0.9307f, 0.9333f, 0.9358f,
            0.9382f, 0.9406f, 0.9429f, 0.9452f, 0.9474f, 0.9495f, 0.9516f,
            0.9536f, 0.9556f, 0.9575f, 0.9594f, 0.9612f, 0.9629f, 0.9646f,
            0.9663f, 0.9679f, 0.9695f, 0.9710f, 0.9725f, 0.9739f, 0.9753f,
            0.9766f, 0.9779f, 0.9791f, 0.9803f, 0.9815f, 0.9826f, 0.9837f,
            0.9848f, 0.9858f, 0.9867f, 0.9877f, 0.9885f, 0.9894f, 0.9902f,
            0.9910f, 0.9917f, 0.9924f, 0.9931f, 0.9937f, 0.9944f, 0.9949f,
            0.9955f, 0.9960f, 0.9964f, 0.9969f, 0.9973f, 0.9977f, 0.9980f,
            0.9984f, 0.9986f, 0.9989f, 0.9991f, 0.9993f, 0.9995f, 0.9997f,
            0.9998f, 0.9999f, 0.9999f, 1.0000f, 1.0000f
    };
    /**
     * The constant Top x
     */
    private final int topX = 0;
    /**
     * Top y
     */
    private final int topY = 0;
    /**
     * Min
     */
    private final int MIN = 0;
    /**
     * Max
     */
    private final int MAX = 100;
    /**
     * The constant Indeterminate state
     */
    public int indeterminateState = -1;
    /**
     * The constant M text paint
     */
    private Paint mTextPaint;
    /**
     * The constant M tick paint
     */
    private Paint mTickPaint;
    /**
     * M background paint
     */
    private Paint mBackgroundPaint;
    /**
     * M progress paint
     */
    private Paint mProgressPaint;
    /**
     * The constant M initial text
     */
    private String mInitialText = "DOWNLOAD";
    /**
     * The constant M text size
     */
    private int mTextSize = 20;
    /**
     * The constant M text color
     */
    private int mTextColor;
    /**
     * M background color
     */
    private int mBackgroundColor;
    /**
     * M progress color
     */
    private int mProgressColor;
    /**
     * The constant M corner radius
     */
    private int mCornerRadius = 10;
    /**
     * The constant M tick bitmap
     */
    private Path mTickPath;
    /**
     * The constant M progress
     */
    private float mProgress = MIN;
    /**
     * The constant M loader type
     */
    private Type mLoaderType = Type.DETERMINATE;
    /**
     * The constant M progress increment factor
     */
    private int mProgressIncrementFactor = 2;
    /**
     * The constant M counter factor
     */
    private int mCounterFactor = 0;
    /**
     * M max counter factor
     */
    private int mMaxCounterFactor = INDETERMINATE_ANIMATION_VALUES.length - 1;
    /**
     * The constant Width
     */
    private int width;
    /**
     * The constant Height
     */
    private int height;
    /**
     * Upgrade SDK 5 to adapt to the onRefresh and onDraw sequences.
     */
    private boolean mIsOnRefreshed = false;
    /**
     * The constant Event handler
     */
    private EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());

    /**
     * Button progress bar
     *
     * @param context context
     */
    public ButtonProgressBar(Context context) {
        super(context);
    }

    /**
     * Button progress bar
     *
     * @param context context
     * @param attrs   attrs
     */
    public ButtonProgressBar(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    /**
     * Initialize view params to user values if attrs != null
     * else provide default values.
     *
     * @param context - view context
     * @param attrs   - attribute set provided by user in layout xml file
     */
    public void init(Context context, AttrSet attrs) {
        mBackgroundColor = AttrUtils.getColorFromAttr(attrs, BUTTONPROGRESSBAR_BGCOLOR, DEFAULT_BGCOLOR);
        mProgressColor = AttrUtils.getColorFromAttr(attrs, BUTTONPROGRESSBAR_PROGCOLOR, DEFAULT_PROGCOLOR);
        mTextColor = AttrUtils.getColorFromAttr(attrs, BUTTONPROGRESSBAR_TEXTCOLOR, Color.WHITE.getValue());
        mTextSize = AttrUtils.getDimensionFromAttr(attrs, BUTTONPROGRESSBAR_TEXTSIZE, mTextSize);

        mInitialText = AttrUtils.getStringFromAttr(attrs, BUTTONPROGRESSBAR_TEXT, mInitialText);
        int type = AttrUtils.getIntFromAttr(attrs, BUTTONPROGRESSBAR_TYPE, 0);
        switch (type) {
            case 0:
                mLoaderType = Type.DETERMINATE;
                break;
            case 1:
                mLoaderType = Type.INDETERMINATE;
                break;
        }

        backgroundPaint();
        progressPaint();
        textPaint();
        tickPaint();
        setLayoutRefreshedListener(this::onRefreshed);
        addDrawTask(this::onDraw);
    }

    /**
     * On refreshed *
     *
     * @param component component
     */
    private void onRefreshed(Component component) {
        width = component.getWidth();
        height = component.getHeight();
        mTickPath = getPathFromVectorDrawable();
    }

    /**
     * On draw *
     *
     * @param component component
     * @param canvas    canvas
     */
    private void onDraw(Component component, Canvas canvas) {
        if (!mIsOnRefreshed) {
            onRefreshed(component);
            mIsOnRefreshed = true;
        }
        if (mLoaderType == Type.DETERMINATE) {
            drawDeterminateProgress(canvas);
        } else {
            drawIndeterminateProgress(canvas);
        }
    }

    /**
     * Background paint
     */
    public void backgroundPaint() {
        mBackgroundPaint = new Paint();
        mBackgroundPaint.setAntiAlias(true);
        mBackgroundPaint.setColor(new Color(mBackgroundColor));
        mBackgroundPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
    }

    /**
     * Progress paint
     */
    public void progressPaint() {
        mProgressPaint = new Paint();
        mProgressPaint.setAntiAlias(true);
        mProgressPaint.setColor(new Color(mProgressColor));
        mProgressPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
    }

    /**
     * Text paint
     */
    public void textPaint() {
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(new Color(mTextColor));
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
    }

    /**
     * Tick paint
     */
    public void tickPaint() {
        mTickPaint = new Paint();
        mTickPaint.setAntiAlias(true);
        mTickPaint.setStrokeWidth(10);
        mTickPaint.setColor(Color.WHITE);
        mTickPaint.setStyle(Paint.Style.STROKE_STYLE);
    }

    /**
     * Set background color *
     *
     * @param bgColor bg color
     */
    public void setBackgroundColor(int bgColor) {
        mBackgroundColor = bgColor;
        mBackgroundPaint.setColor(new Color(bgColor));
        invalidate();
    }

    /**
     * Set progress color *
     *
     * @param progColor prog color
     */
    public void setProgressColor(int progColor) {
        mProgressColor = progColor;
        mProgressPaint.setColor(new Color(progColor));
        invalidate();
    }

    /**
     * Set text color *
     *
     * @param textColor text color
     */
    public void setTextColor(int textColor) {
        mTextColor = textColor;
        mTextPaint.setColor(new Color(textColor));
        invalidate();
    }

    /**
     * Set text size *
     *
     * @param size size
     */
    public void setTextSize(int size) {
        mTextSize = size;
        mTextPaint.setTextSize(size);
        invalidate();
    }

    /**
     * Get path from vector drawable path
     *
     * @return the path
     */
    private Path getPathFromVectorDrawable() {
        int min = Math.min(width, height);
        Path path = new Path();
        path.moveTo(width / 2 - min / 4, height / 2);
        path.lineTo(width / 2, height / 2 + min / 4);
        path.lineTo(width / 2 + min / 2, height / 2 - min / 4);
        return path;
    }

    /**
     * Get loader type type
     *
     * @return the type
     */
    public Type getLoaderType() {
        return this.mLoaderType;
    }

    /**
     * Set Loader type to determinate or indeterminate
     *
     * @param mLoaderType m loader type
     */
    public void setLoaderType(Type mLoaderType) {
        this.mLoaderType = mLoaderType;
    }

    /**
     * Draw indeterminate progress *
     *
     * @param canvas canvas
     */
    private void drawIndeterminateProgress(Canvas canvas) {
        switch (indeterminateState) {
            case STATE_RESET:
                onDrawInit(canvas);
                break;
            case STATE_START:
                updateProgress();
                onDrawProgress(canvas);
                eventHandler.postTask(new Runnable() {
                    @Override
                    public void run() {
                        invalidate();
                    }
                });
                break;
            case STATE_STOP:
                onDrawFinished(canvas);
                break;
        }
    }

    /**
     * Update mProgress value based on static pre-calculated value and incrementFactor
     */
    public void updateProgress() {
        if (mProgressIncrementFactor == 4) {
            mProgressIncrementFactor = 1;
        }
        if (mCounterFactor >= mMaxCounterFactor) {
            mCounterFactor = 1;
        }
        mProgress = INDETERMINATE_ANIMATION_VALUES[mCounterFactor] * MAX;
        if (mProgress <= 1) {
            mProgress = MIN;
        }
        mCounterFactor += mProgressIncrementFactor;
        mProgressIncrementFactor++;
    }

    /**
     * Draw determinate progress *
     *
     * @param canvas canvas
     */
    private void drawDeterminateProgress(Canvas canvas) {
        if (mProgress == MIN) {
            onDrawInit(canvas);
        } else if (mProgress > MIN && mProgress < MAX) {
            onDrawProgress(canvas);
        } else {
            onDrawFinished(canvas);
        }
    }

    /**
     * draw initial state of mProgress view when view is either created or in reset state.
     *
     * @param canvas - view canvas object
     */
    public void onDrawInit(Canvas canvas) {
        RectFloat bgRectf = new RectFloat(topX, topY, width, height);
        canvas.drawRoundRect(bgRectf, mCornerRadius, mCornerRadius, mBackgroundPaint);
        Rect bounds = new Rect();
        bounds = mTextPaint.getTextBounds(mInitialText);
        int xPos = (width - bounds.getWidth()) / 2;
        int textAdjust = (int) (mTextPaint.descent() + mTextPaint.ascent()) / 2;
        int yPos = ((height / 2) - textAdjust);
        canvas.drawText(mTextPaint, mInitialText, xPos, yPos);
    }

    /**
     * Draw mProgress on view based on mLoaderType.
     *
     * @param canvas - view canvas object
     */
    public void onDrawProgress(Canvas canvas) {
        RectFloat bgRectf = new RectFloat(topX, topY, width, height);
        canvas.drawRoundRect(bgRectf, mCornerRadius, mCornerRadius, mBackgroundPaint);
        float progressPoint = (((float) width / MAX) * mProgress);
        RectFloat progRect = new RectFloat(topX, topY, progressPoint, height);
        canvas.drawRoundRect(progRect, mCornerRadius, mCornerRadius, mProgressPaint);
    }

    /**
     * Draw final state of mProgress view when mProgress is completed.
     *
     * @param canvas - view canvas object
     */
    public void onDrawFinished(Canvas canvas) {
        RectFloat progRect = new RectFloat(topX, topY, width, height);
        canvas.drawRoundRect(progRect, mCornerRadius, mCornerRadius, mProgressPaint);
        if (mTickPath != null) {
            canvas.drawPath(mTickPath, mTickPaint);
        }
    }

    /**
     * if view.Type == DETERMINATE then set mProgress to argument and invalidate() view
     *
     * @param currentProgress - int mLoaderType mProgress indeterminateState varying between mMinProgress and mMaxProgress.
     */
    public void setProgress(int currentProgress) {
        if (mLoaderType == Type.DETERMINATE) {
            mProgress = currentProgress;
            invalidate();
        }
    }

    /**
     * set indeterminateState to 1 then invalidate() view
     */
    public void startLoader() {
        indeterminateState = STATE_START;
        invalidate();
    }

    /**
     * set indeterminateState to 0 then invalidate() view
     */
    public void stopLoader() {
        indeterminateState = STATE_STOP;
        invalidate();
    }

    /**
     * Reset State back to initialState
     * if view.Type == INDETERMINATE then set indeterminateState to -1
     * else set currentProgress to 0
     * finally invalidate() view
     */
    public void reset() {
        if (mLoaderType == Type.INDETERMINATE) {
            indeterminateState = STATE_RESET;
        }
        mProgress = MIN;
        invalidate();
    }

    /**
     * Enum for loader mLoaderType { determinate or indeterminate }
     */
    public enum Type {
        /**
         * Determinate type
         */
        DETERMINATE,
        /**
         * Indeterminate type
         */
        INDETERMINATE
    }
}
